#include "common.h"

#define MAX_VIDEOS  32

struct IVid g_videos[MAX_VIDEOS];


DLL_EXPORT void vidInit(void)
{
    int i;
    for (i = 0; i < MAX_VIDEOS; i++)
    {
        vid_init(&g_videos[i], i);
    }
    av_register_all();
}

DLL_EXPORT VidSource* vidOpen(const char* filename)
{
    int i;
    struct IVid* vid = NULL;
    AVFormatContext* pFormatCtx = NULL;
    AVCodecContext* pCodecCtx = NULL;
    int videoStream = -1;
    AVCodec* pCodec = NULL;

    //
    //load ffmpeg
    //
    //open file
    if (av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL) != 0)
    {
        printf("Error opening file '%s'.\n", filename);
        return NULL;
    }
    //chack streams
    if (av_find_stream_info(pFormatCtx) < 0)
    {
        printf("Error finding stream information.\n");
        return NULL;
    }
    dump_format(pFormatCtx, 0, filename, 0);
    //find video stream
    for (i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
        {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1)
    {
        printf("Error finding video stream.\n");
        return NULL;
    }
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    //find codec for the video stream
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL)
    {
        printf("Error: unsupported codec!\n");
        return NULL;
    }
    //open codec
    if (avcodec_open(pCodecCtx, pCodec) < 0)
    {
        printf("Could not open codec!\n");
        return NULL;
    }
    AVStream* avs = pFormatCtx->streams[videoStream];

    //find free video
    for (i = 0; i < MAX_VIDEOS; i++)
    {
        if (g_videos[i].used == 0)
        {
            vid = &g_videos[i];
            break;
        }
    }
    if (!vid)
        return NULL;

    //init IVid
    vid->used = 1;
    vid->fc = pFormatCtx;
    vid->cc = pCodecCtx;
    vid->video_stream = videoStream;
    vid->frame_yuv = avcodec_alloc_frame();
    vid->frame_rgb = avcodec_alloc_frame();
    vid->fps = (double)avs->r_frame_rate.num / (double)avs->r_frame_rate.den;
    //input data
    vid->src_width = pCodecCtx->width;
    vid->src_height = pCodecCtx->height;
    vid->src_format = pCodecCtx->pix_fmt;
    //output data
    vid->dst_width = min_pow2(vid->src_width);
    vid->dst_height = min_pow2(vid->src_height);
    vid->dst_format = PIX_FMT_BGRA;
    vid->dst_data = malloc(vid->dst_width * vid->dst_height * 4);
    //decoding buffer
    vid->buffer_size = avpicture_get_size(vid->dst_format, vid->dst_width, vid->dst_height);
    vid->buffer = malloc(vid->buffer_size);
    avpicture_fill((AVPicture*)vid->frame_rgb, vid->buffer, vid->dst_format,
                   vid->dst_width, vid->dst_height);
    //init frames
    vid->fifo_depth = 8;
    vid->fifo_frames = malloc(sizeof(struct IVidFrame) * vid->fifo_depth);
    for (i = 0; i < vid->fifo_depth; i++)
    {
        vid->fifo_frames[i].data = malloc(vid->dst_width * vid->dst_height * 4);
        sem_init(&vid->fifo_frames[i].read_ok, PTHREAD_PROCESS_PRIVATE, 0);
        sem_init(&vid->fifo_frames[i].write_ok, PTHREAD_PROCESS_PRIVATE, 1);
    }

    //create source
    VidSource* src = malloc(sizeof(VidSource));
    src->index = vid->index;
    src->current_time = 0.0;
    src->total_time = 0.0;
    src->video_width = vid->src_width;
    src->video_height = vid->src_height;
    src->video_fps = vid->fps;

    vid->vsrc = src;
    stream_start(vid);

    return src;
}

DLL_EXPORT void vidClose(VidSource* src)
{
    if (!src)
        return;
    struct IVid* vid = &g_videos[src->index];
    int i;
    stream_stop(vid);
    while (vid->thr_running)
        usleep(120);
    for (i = 0; i < vid->fifo_depth; i++)
        free(vid->fifo_frames[i].data);
    free(vid->fifo_frames);
    free(vid->dst_data);
    av_free(vid->buffer);
    av_free(vid->frame_rgb);
    av_free(vid->frame_yuv);
    avcodec_close(vid->cc);
    av_close_input_file(vid->fc);
    pthread_mutex_destroy(&vid->msg_mutex);
    //reinit
    vid_init(vid, src->index);
    free(src);
}

DLL_EXPORT int vidUpdate(VidSource* src, double dt)
{
    struct IVid* vid = &g_videos[src->index];
    struct IVidFrame* frm = &vid->fifo_frames[vid->frame_counter];

    vid->frame_time -= dt;
    if (vid->frame_time < 0.0)
    {
        if (sem_trywait(&frm->read_ok) != 0)
            return 0;

        //swap data pointers
        void* tmp_data = frm->data;
        frm->data = vid->dst_data;
        vid->dst_data = tmp_data;

        vid->frame_time += 1.0 / vid->fps;
        src->current_time += 1.0 / vid->fps;

        sem_post(&frm->write_ok);
        vid->frame_counter++;
        if (vid->frame_counter == vid->fifo_depth)
            vid->frame_counter = 0;
        return 1;
    }

    return 0;
}

DLL_EXPORT void vidGetFrame(VidSource* src, VidFrame* out_frm)
{
    struct IVid* vid = &g_videos[src->index];
    //vid_read_frame(vid, frm->data);
    out_frm->width = vid->dst_width;
    out_frm->height = vid->dst_height;
    out_frm->data = vid->dst_data;
    out_frm->index = vid->frame_counter;
}

DLL_EXPORT void vidSendMsg(VidSource* src, VidMsg msg, int param)
{
    struct IVid* vid = &g_videos[src->index];
    pthread_mutex_lock(&vid->msg_mutex);
    vid->msg = msg;
    vid->msg_param = param;
    pthread_mutex_unlock(&vid->msg_mutex);
}



